//********************************************************************************************************************//
//ORGANIZATION:
//AUTHOR: bpeng(bogers.peng@gmail.com)
//SUMMARY:
//********************************************************************************************************************//
#ifndef NATIVE_SYSTEM_BASE_ARRAY_H
#define NATIVE_SYSTEM_BASE_ARRAY_H

#ifndef NATIVE_SYSTEM_BASE_COLLECTION_H
#include "System/Base/Collection.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_FILTER_H
#include "System/Base/Filter.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_ARRAYCURSOR_H
#include "System/Base/ArrayCursor.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_RANGE_H
#include "System/Base/Range.h"
#endif

namespace Native { namespace System
{
    //****************************************************************************************************************//
    //CArray
    //
    //****************************************************************************************************************//
    template<typename _TElement>
    class NATIVE_LIBRARY_EXPORT CArray :
        public CCollection,
        public IFilter<_TElement>
    {
        NATIVE_DECLARE_OBJECT;

        NATIVE_REDEFINE(public, _TElement, TElement, T_ELEMENT);
        NATIVE_REDEFINE(public, typename vector<TElement>::size_type, VectorSizeType, VECTOR_SIZE_TYPE);
        NATIVE_REDEFINE(public, CArray<TElement>, Class, CLASS);

    protected:
        static inline bool _ValidateRange(CRange<size_t>::T_NUMERIC& point, CRange<size_t>::CLASS& range)
        {
            return range.InCloseRange(point);
        }

    protected:
        vector<TElement> _data;

        CArrayCursor _At(const size_t& index) const throw()
        {
            try {
                if (!_data.empty() && _ValidateRange(index, CRange<size_t>(0, _data.size()-1))) {
                    return CArrayCursor(index);
                } else {
                    return CArrayCursor(CArrayCursor::INEXISTENT);
                }
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"_At", e);
            }
        }

    public:
        CArray()
        {};

        CArray(CLASS& that) :
            CCollection(that),
            _data(that._data)
        {};

        CArray(CLASS&& that) :
            CCollection(that),
            _data(that._data)
        {};

        CArray(const vector<TElement>& vector) :
            _data(vector)
        {};

        CArray(T_ELEMENT array[], size_t count) throw()
        {
            CNullPointerException::Check(NATIVE_SCENE(L"CArray"), (POINTER)array, L"array");

            for (size_t i = 0; i < count; i++) {
                _data.push_back(array[i]);
            }
        };

        virtual ~CArray()
        {};

    public:
        //{CCollection
        virtual size_t Count() const
        {
            return _data.size();
        };

        virtual bool Empty() const
        {
            return _data.empty();
        };

        virtual void Clear()
        {
            _data.clear();
        }
        //}
        //{Selectable
        vector<TElement> Select(function<bool(const TElement&)> have) const
        {
            vector<TElement> result;

            for (int i = 0; i < _data.size(); i++) {
                if (have(_data[i])) {
                    result.push_back(_data[i]);
                }
            }

            return move(result);
        }
        //}
        CArrayCursor At(const size_t& index) const throw()
        {
            try {
                return _At(index);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"At", e);
            }
        };

        CArrayCursor At(const size_t& index) throw()
        {
            try {
                return _At(index);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"At", e);
            }
        };

        CArrayCursor LocateFirst() const throw()
        {
            try {
                return _At(0);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"LocateFirst", e);
            }
        };

        CArrayCursor LocateLast() const throw()
        {
            try {
                return _At(_data.size()-1);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"LocateLast", e);
            }
        };

        bool AtBeginning(size_t index) const
        {
            return _data.empty()? false : index == 0;
        };

        bool AtEnding(size_t index) const
        {
            return _data.empty()? false : index == _data.size()-1;
        }

        const CArray& Append(const TElement& data)
        {
            _data.push_back(data);

            return *this;
        };

        TElement Get(size_t index) const throw()
        {
            CZeroValueException<VectorSizeType>::Check(NATIVE_SCENE(L"Get"), _data.size());
            COutOfRangeException<VectorSizeType>::Check(
                NATIVE_SCENE(L"Get"),
                index,
                CRange<VectorSizeType>(0, _data.size()-1),
                _ValidateRange, L"index");

            return _data[index];
        };

        TElement& Refer(size_t index) throw()
        {
            CZeroValueException<VectorSizeType>::Check(NATIVE_SCENE(L"Refer"), _data.size());
            COutOfRangeException<VectorSizeType>::Check(
                NATIVE_SCENE(L"Refer"),
                index,
                CRange<VectorSizeType>(0, _data.size()-1),
                _ValidateRange,
                L"index");

            return _data[index];
        };

        void Assign(size_t index, T_ELEMENT& element) throw()
        {
            CZeroValueException<VectorSizeType>::Check(NATIVE_SCENE(L"Assign"), _data.size());
            COutOfRangeException<VectorSizeType>::Check(
                NATIVE_SCENE(L"Assign"),
                index,
                CRange<VectorSizeType>(0, _data.size()-1),
                _ValidateRange,
                L"index");

            _data[index] = element;
        };
        
        const CArray& operator=(CLASS& rvalue)
        {
            _data = rvalue._data;

            return *this;
        };

        TElement& operator[](size_t index) throw()
        {
            try {
                return Refer(index);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"operator[]", e);
            }
        };
    };
}}

#endif